#include "sct/mux/tshead.h"
#include "sct/mux/tsmux.h"

namespace sct
{

void CTSMux::putTSPacket(const  void * p)
{
    if(mCallBack)
    {
    	mCallBack->onTsPacketBuilded(this, p);
    }
}

CTSMux::CTSMux(const int id):
		mPATCnt(0),
		mNullCnt(0),
		mTsStreamId(id),
		mCallBack(NULL)
{
}

CTSMux::CProgramInfo* CTSMux::createProgram(const int program)
{
	std::list<CProgramInfo *>::iterator it;
	for(it=mPrograms.begin(); it!=mPrograms.end(); it++)
	{
		if(program == (*it)->program)
		{
			break;
		}
	}

	if(it==mPrograms.end())
	{
		///not found
		CProgramInfo* info = new CProgramInfo;
		info->program = program;
		mPrograms.push_back(info);

		return info;
	}

	return NULL;
}

class TypeMap
{
public:
	CTSMux::StreamType streamType;
	MpegTsStream ts_type;
	MpegTsPESStreamId ts_id;
};

static TypeMap gTypeMap[] = {
		{(CTSMux::StreamType)0, (MpegTsStream)0, (MpegTsPESStreamId)0},
		{CTSMux::ST_H264, MpegTsStreamVideoAVC, MpegTsPESStreamIdVideoCommon},
		{CTSMux::ST_AAC, MpegTsStreamAudioAAC, MpegTsPESStreamIdAudioCommon},
};

CTSMux::StreamType CTSMux::getStreamType(const int tsStreamType)
{
    int sz = sizeof(gTypeMap)/sizeof(gTypeMap[0]);

    for(int i = 0; i<sz; i++)
    {
        if(gTypeMap[i].ts_type == tsStreamType)
        {
            return gTypeMap[i].streamType;
        }
    }

    return gTypeMap[0].streamType;
}


CTSMux::CStreamInfo* CTSMux::createStream(CProgramInfo* program, const StreamType type)
{
	CStreamInfo*stream=new CStreamInfo;

	stream->id = gTypeMap[type].ts_id;
	stream->type = gTypeMap[type].ts_type;

	program->streams.push_back(stream);

	return stream;
}

void CTSMux::init()
{
	///assigned pid
	int pid = 0x200;

	std::list<CProgramInfo *>::iterator program;
	for(program = mPrograms.begin(); program != mPrograms.end(); program++)
	{
		(*program)->pid=pid++;
		(*program)->cnt=0;

		(*program)->pcr_pid=pid++;
		(*program)->pcr_cnt=0;

		std::list<CStreamInfo *>::iterator stream;
		for(stream = (*program)->streams.begin();
				stream != (*program)->streams.end();
				stream++)
		{
			(*stream)->pid=pid++;
			(*stream)->cnt=0;
		}
	}
}

uint8_t *CTSMux::writePts(uint8_t *p, const int fb, const int64_t pts)
{
	unsigned int val;

	val = fb << 4 | (((pts >> 30) & 0x07) << 1) | 1;
	*p++ = (unsigned char)val;

	val = (((pts >> 15) & 0x7fff) << 1) | 1;
	*p++ = (unsigned char)(val >> 8);
	*p++ = (unsigned char)val;

	val = (((pts)& 0x7fff) << 1) | 1;
	*p++ = (unsigned char)(val >> 8);
	*p++ = (unsigned char)val;

	return p;
}


void CTSMux::buildPESPacket(CStreamInfo*stream, const void * p3, const int len1,
			const int64_t pts,
			const int64_t dts)
{
	MPEGTS_FRAME_T frame;
	frame.dts = dts;
	frame.pts = pts;
	frame.pid = stream->pid;

	int & av_cnt = stream->cnt;
	MPEGTS_FRAME_T * mpegtsFrame = &frame;
	unsigned char *avBuf = (unsigned char*)p3;
	int len = len1;
	bool first = true;
	unsigned int pes_size = 0;
	int stuff_size = 0, avPos =0, body_size, in_size;
	unsigned char packet[MPEGTS_PACKET_SIZE], *p, *base;

	while (len > 0)
	{
		p = packet;

		MPEGTS_HEADER_T *mpegtsHeader = (MPEGTS_HEADER_T *)p;
		mpegtsHeader->sync_byte = 0x47;
		mpegtsHeader->transport_error_indicator = 0;
		mpegtsHeader->payload_unit_start_indicator = 0;
		mpegtsHeader->transport_priority = 0;
		mpegtsHeader->pid_12to8 = (mpegtsFrame->pid >> 8) & 0x1f;
		mpegtsHeader->pid_7to0 = mpegtsFrame->pid & 0xff;
		mpegtsHeader->transport_scrambling_control = 0;
		mpegtsHeader->adaptation_field_control = 0x01;//
		mpegtsHeader->continuity_counter = av_cnt;
		++av_cnt;
		av_cnt = av_cnt % 0x10;

		//SCT_LOGD("%d", av_cnt);

		p += sizeof(MPEGTS_HEADER_T);
		if (first)
		{
			mpegtsHeader->payload_unit_start_indicator = 1;

			MPEGTS_PES_HEADER_T *pesHeader = (MPEGTS_PES_HEADER_T *)p;
			pesHeader->packet_start_code_23to16 = 0x00;
			pesHeader->packet_start_code_15to8 = 0x00;
			pesHeader->packet_start_code_7to0 = 0x01;
			pesHeader->stream_id = stream->id;
			pesHeader->pes_packet_length_15to8 = 0x00;
			pesHeader->pes_packet_length_7to0 = 0x00;
			pesHeader->marker_bits = 0x02;
			pesHeader->pes_scrambling_control = 0x00;
			pesHeader->pes_priority = 0x00;
			pesHeader->data_alignment_indicator = 0x00;
			pesHeader->copyright = 0x00;
			pesHeader->original_or_copy = 0x01;
			//pesHeader->pts_dts_flags = pts_dts_flags;//
			pesHeader->escr_flag = 0x00;
			pesHeader->es_rate_flag = 0x00;
			pesHeader->dsm_trick_mode_flag = 0x00;
			pesHeader->add_copy_info_flag = 0x00;
			pesHeader->pes_crc_flag = 0x00;
			pesHeader->pes_ext_flag = 0x00;
			pesHeader->header_data_length = 0x0A;

			//pts & dts
			p += sizeof(MPEGTS_PES_HEADER_T);
			if (/*pts_dts_flags == 0x03*/mpegtsFrame->pts != mpegtsFrame->dts)
			{
				pesHeader->pts_dts_flags = 0x3;
				pesHeader->header_data_length = 0x0A;
				p = writePts(p, 3, mpegtsFrame->pts);//pts
				p = writePts(p, 1, mpegtsFrame->dts);//dts
			}
			else
			{
				pesHeader->pts_dts_flags = 0x2;
				pesHeader->header_data_length = 0x05;
				p = writePts(p, 2, mpegtsFrame->pts);//pts
			}

			pes_size += pesHeader->header_data_length + len + 3;
			if (pes_size > 0xffff)
				pes_size = 0;

			pesHeader->pes_packet_length_15to8 = (pes_size >> 8) & 0xff;
			pesHeader->pes_packet_length_7to0 = pes_size & 0xff;

			//p += sizeof(MPEGTS_PES_HEADER_T) + pesHeader->header_data_length;
			first = false;
		}
		body_size = (int)(packet + sizeof(packet) - p);
		in_size = len;

		if (body_size <= in_size)
		{
			memcpy(p, avBuf + avPos, body_size);
			avPos += body_size;
			len -= body_size;
		}
		else
		{
			stuff_size = body_size - in_size;
			//TODO:fixme

			if (packet[3] & 0x20)
			{
				base = &packet[5] + packet[4];
				memmove(base + stuff_size, base, p - base);
				p += stuff_size;
				memset(base, 0xff, stuff_size);
				packet[4] += (unsigned char)stuff_size;

			}
			else
			{
				packet[3] |= 0x20;
				memmove(&packet[4] + stuff_size, &packet[4], p - &packet[4]);
				p += stuff_size;
				packet[4] = (unsigned char)(stuff_size - 1);
				if (stuff_size >= 2) {
					packet[5] = 0;
					memset(&packet[6], 0xff, stuff_size - 2);
				}
			}
			memcpy(p, avBuf + avPos, in_size);
			avPos += in_size;
			len -= in_size;
		}
		putTSPacket(packet);
	}

}
CTSMux::~CTSMux()
{
	while(!mPrograms.empty())
	{
		CProgramInfo * program = mPrograms.front();
		mPrograms.pop_front();

		while(!program->streams.empty())
		{
			CStreamInfo* stream = program->streams.front();
			program->streams.pop_front();

			SCT_DELETE(stream);
		}

		SCT_DELETE(program);
	}

}

void CTSMux::buildPATPacket()
{
	uint8_t pkt[188];
	int & pat_cnt = mPATCnt;
	MPEGTS_HEADER_T *mpegtsHeader = (MPEGTS_HEADER_T *)pkt;
	//patBuf[4] means pointer_field
	MPEGTS_PAT_HEADER_T *mpegtsPatHeader = (MPEGTS_PAT_HEADER_T *)(pkt + 5);
	unsigned char *patPayload = (unsigned char*)((unsigned char*)mpegtsPatHeader + sizeof(MPEGTS_PAT_HEADER_T));

	mpegtsHeader->sync_byte = 0x47;
	mpegtsHeader->transport_error_indicator = 0;
	mpegtsHeader->payload_unit_start_indicator = 1;
	mpegtsHeader->transport_priority = 0;
	mpegtsHeader->pid_12to8 = (MpegTsPidPAT >> 8) & 0x1f;
	mpegtsHeader->pid_7to0 = MpegTsPidPAT & 0xff;
	mpegtsHeader->transport_scrambling_control = 0;
	mpegtsHeader->adaptation_field_control = MpegTsAdaptationFieldTypePayloadOnly;
	mpegtsHeader->continuity_counter = pat_cnt & 0xf;
	pat_cnt++;
	pat_cnt = pat_cnt % 0x10;

	pkt[4] = 0x00;

	mpegtsPatHeader->table_id = 0x00;
	mpegtsPatHeader->section_syntax_indicator = 1;
	mpegtsPatHeader->b0 = 0;
	mpegtsPatHeader->reserved0 = 0x3;
	mpegtsPatHeader->section_length8to11 = 0; // calc in size.
	mpegtsPatHeader->section_length0to7 = 0;
	mpegtsPatHeader->transport_stream_id_h = mTsStreamId>>8;
	mpegtsPatHeader->transport_stream_id_l = mTsStreamId;
	mpegtsPatHeader->reserved1 = 0x3;
	mpegtsPatHeader->version_number = 0;
	mpegtsPatHeader->current_next_indicator = 1;
	mpegtsPatHeader->section_number = 0x0;
	mpegtsPatHeader->last_section_number = 0x0;


	std::list<CProgramInfo *>::iterator it;
	for(it=mPrograms.begin(); it!=mPrograms.end(); it++)
	{
		//program_number
		patPayload[0] = (*it)->program>>8;
		patPayload[1] = (*it)->program;
		//program_map_PID
		patPayload[2] = 0xe0 | (((*it)->pid & 0x1fff) >> 8);
		patPayload[3] = (*it)->pid & 0xff;
		patPayload += 4;
	}

	unsigned int section_len = patPayload + 4 - ((unsigned char *)mpegtsPatHeader + 3);
	mpegtsPatHeader->section_length8to11 = (section_len & 0xfff) >> 8;
	mpegtsPatHeader->section_length0to7 = section_len & 0xff;

	unsigned int crc32 = getCrc32((unsigned char *)mpegtsPatHeader, (int)(patPayload - (unsigned char *)mpegtsPatHeader));
	patPayload[0] = (crc32 >> 24) & 0xff;
	patPayload[1] = (crc32 >> 16) & 0xff;
	patPayload[2] = (crc32 >> 8) & 0xff;
	patPayload[3] = crc32 & 0xff;
	patPayload += 4;

	memset(patPayload, 0xff, MPEGTS_PACKET_SIZE - (patPayload - pkt));

	putTSPacket(pkt);
}

void CTSMux::buildPMTPacket()
{
	std::list<CProgramInfo *>::iterator program;
	for(program=mPrograms.begin(); program!=mPrograms.end(); program++)
	{
		_buildPMTPacket(*program);
	}
}
void CTSMux::_buildPMTPacket(CProgramInfo * program)
{
	int & pmt_cnt = program->cnt;

	uint8_t pkt[188];
	MPEGTS_HEADER_T *mpegtsHeader = (MPEGTS_HEADER_T *)pkt;
	//pmtBuf[4] means pointer_field
	MPEGTS_PMT_HEADER_T *mpegtsPmtHeader = (MPEGTS_PMT_HEADER_T *)(pkt + 5);
	unsigned char *pmtPayload = (unsigned char*)((unsigned char*)mpegtsPmtHeader + sizeof(MPEGTS_PMT_HEADER_T));

	mpegtsHeader->sync_byte = 0x47;
	mpegtsHeader->transport_error_indicator = 0;
	mpegtsHeader->payload_unit_start_indicator = 1;
	mpegtsHeader->transport_priority = 0;
	mpegtsHeader->pid_12to8 = (program->pid >> 8) & 0x1f;
	mpegtsHeader->pid_7to0 = program->pid & 0xff;
	mpegtsHeader->transport_scrambling_control = 0;
	mpegtsHeader->adaptation_field_control = MpegTsAdaptationFieldTypePayloadOnly;
	mpegtsHeader->continuity_counter = pmt_cnt & 0xf;
	++pmt_cnt;
	pmt_cnt = pmt_cnt % 0x10;

	pkt[4] = 0x00;

	mpegtsPmtHeader->table_id = 0x02;
	mpegtsPmtHeader->section_syntax_indicator = 1;
	mpegtsPmtHeader->b0 = 0;
	mpegtsPmtHeader->reserved0 = 0x3;
	mpegtsPmtHeader->section_length8to11 = 0;
	mpegtsPmtHeader->section_length0to7 = 0;
	mpegtsPmtHeader->program_number_h = program->program>>8;
	mpegtsPmtHeader->program_number_l = program->program;
	mpegtsPmtHeader->reserved1 = 0x3;
	mpegtsPmtHeader->version_number = 0;
	mpegtsPmtHeader->current_next_indicator = 1;
	mpegtsPmtHeader->section_number = 0x00;
	mpegtsPmtHeader->last_section_number = 0x00;
	mpegtsPmtHeader->reserved2 = 0x7;

	mpegtsPmtHeader->PCR_PID8to12 = (program->pcr_pid & 0x1fff) >> 8;
	mpegtsPmtHeader->PCR_PID0to7 = program->pcr_pid & 0xff;

	mpegtsPmtHeader->reserved3 = 0xf;
	mpegtsPmtHeader->program_info_length8to11 = 0;
	mpegtsPmtHeader->program_info_length0to7 = 0;

	std::list<CStreamInfo*>::iterator streamit;
	for(streamit=program->streams.begin(); streamit!=program->streams.end(); streamit++)
	{
		MPEGTS_PMT_ELEMENT_T *mpegtsPmtElement1 = (MPEGTS_PMT_ELEMENT_T *)pmtPayload;
		mpegtsPmtElement1->stream_type = (*streamit)->type;
		mpegtsPmtElement1->reserved0 = 0x7;
		mpegtsPmtElement1->elementary_PID8to12 = ((*streamit)->pid & 0x1fff) >> 8;
		mpegtsPmtElement1->elementary_PID0to7 = (*streamit)->pid & 0xff;
		mpegtsPmtElement1->reserved1 = 0xf;
		mpegtsPmtElement1->ES_info_length_h = 0;
		mpegtsPmtElement1->ES_info_length_l = 0/*0x3*/;

		pmtPayload += sizeof(MPEGTS_PMT_ELEMENT_T);
	}

	unsigned int section_len = pmtPayload + 4 - ((unsigned char *)mpegtsPmtHeader + 3);
	mpegtsPmtHeader->section_length8to11 = (section_len & 0xfff) >> 8;
	mpegtsPmtHeader->section_length0to7 = section_len & 0xff;

	unsigned int crc32 = getCrc32((unsigned char *)mpegtsPmtHeader, (int)(pmtPayload - (unsigned char *)mpegtsPmtHeader));
	pmtPayload[0] = (crc32 >> 24) & 0xff;
	pmtPayload[1] = (crc32 >> 16) & 0xff;
	pmtPayload[2] = (crc32 >> 8) & 0xff;
	pmtPayload[3] = crc32 & 0xff;
	pmtPayload += 4;

	memset(pmtPayload, 0xff, MPEGTS_PACKET_SIZE - (pmtPayload - pkt));

	putTSPacket(pkt);
}

void CTSMux::buildPCRPacket(CProgramInfo* program, const int64_t pcr)
{
	int & pcr_cnt = program->pcr_cnt;

	uint8_t pkt[188];
	MPEGTS_HEADER_T *mpegtsHeader = (MPEGTS_HEADER_T *)pkt;

	mpegtsHeader->sync_byte = 0x47;
	mpegtsHeader->transport_error_indicator = 0;
	mpegtsHeader->payload_unit_start_indicator = 0;
	mpegtsHeader->transport_priority = 0;
	mpegtsHeader->pid_12to8 = (program->pcr_pid >> 8) & 0x1f;
	mpegtsHeader->pid_7to0 = program->pcr_pid & 0xff;
	mpegtsHeader->transport_scrambling_control = 0;
	mpegtsHeader->adaptation_field_control = MpegTsAdaptationFieldTypeAdaptionOnly;
	mpegtsHeader->continuity_counter = pcr_cnt & 0xf;
//	++pcr_cnt;
	pcr_cnt = pcr_cnt % 0x10;

	MPEGTS_ADAPTATION_FIELD_HEADER_T *mpegtsAdaptationFieldHeader = (MPEGTS_ADAPTATION_FIELD_HEADER_T *)(pkt + 4);
	mpegtsAdaptationFieldHeader->adaptation_field_length = MPEGTS_PACKET_SIZE - 4 - 1;
	mpegtsAdaptationFieldHeader->discontinuity_indicator = 0;
	mpegtsAdaptationFieldHeader->random_access_indicator = 0;
	mpegtsAdaptationFieldHeader->elementary_stream_priority_indicator = 0;
	mpegtsAdaptationFieldHeader->pcr_flag = 1;
	mpegtsAdaptationFieldHeader->opcr_flag = 0;
	mpegtsAdaptationFieldHeader->splicing_point_flag = 0;
	mpegtsAdaptationFieldHeader->transport_private_data_flag = 0;
	mpegtsAdaptationFieldHeader->adaptation_field_extension_flag = 0;

	unsigned char *adaptationFieldPayload = (unsigned char*)((unsigned char*)mpegtsAdaptationFieldHeader + sizeof(MPEGTS_ADAPTATION_FIELD_HEADER_T));
	*adaptationFieldPayload++ = (unsigned char)(pcr >> 25);
	*adaptationFieldPayload++ = (unsigned char)(pcr >> 17);
	*adaptationFieldPayload++ = (unsigned char)(pcr >> 9);
	*adaptationFieldPayload++ = (unsigned char)(pcr >> 1);
	*adaptationFieldPayload++ = (unsigned char)(pcr << 7 | 0x7e);
	*adaptationFieldPayload++ = 0;

	memset(adaptationFieldPayload, 0xff, MPEGTS_PACKET_SIZE - (adaptationFieldPayload - pkt));

	putTSPacket(pkt);
}

void CTSMux::buildNULLPacket()
{
	int&cnt = mNullCnt;

	uint8_t pkt[188];
	MPEGTS_HEADER_T *mpegtsHeader = (MPEGTS_HEADER_T *)pkt;

	mpegtsHeader->sync_byte = 0x47;
	mpegtsHeader->transport_error_indicator = 0;
	mpegtsHeader->payload_unit_start_indicator = 0;
	mpegtsHeader->transport_priority = 0;
	mpegtsHeader->pid_12to8 = (MPEGTS_NULL_PACKET_PID >> 8) & 0x1f;
	mpegtsHeader->pid_7to0 = MPEGTS_NULL_PACKET_PID & 0xff;
	mpegtsHeader->transport_scrambling_control = 0;
	mpegtsHeader->adaptation_field_control = MpegTsAdaptationFieldTypePayloadOnly;
	mpegtsHeader->continuity_counter = cnt;
	cnt++;
	cnt = cnt%0x10;

	memset(pkt + sizeof(MPEGTS_HEADER_T), 0xff, MPEGTS_PACKET_SIZE - sizeof(MPEGTS_HEADER_T));
	putTSPacket(pkt);
}

}//end namespace
